#include "ExchangeIO.h"
#include "exchange_step.h"
#include "common/system_log.h"
#include "exchange_obj.h"
#include "exchange_stl.h"
#include "exchange_gltf.h"
#include "exchange_brep.h"
#include "exchange_dxf.h"
#include "exchange_dwg.h"

#include "utils/PMIHelper.h"
#include "utils/file_util.h"
#include "exchange_amx.h"
#include "utils/string_util.h"
#include <memory>
#include <vector>
#include "ExchangeBase.h"

#ifdef INTEROP_ENABLE
#include "FusionConvert.h"
#include "ExchangeInterOp.h"
#include "InterOpIO.h"
#endif

#include "bridge/ExchangeHelper.h"
#include "ExchangeDocumentInstance.h"
#include "ExchangeDocumentReference.h"
#include "ExchangeObject.h"
#include "ExchangeTopoShape.h"

AMCAX_EXCHANGE_NAMESPACE_BEGIN

// std::shared_ptr<ExchangeIO> create_p() 
// {
//     return std::make_shared<Chlid>();
// }

static std::unordered_map<std::string, EXCHANGE_FILE_TYPE> FILE_EXTENSION_NAME_TYPE_MAP =
{
    {".step", EXCHANGE_FILE_TYPE::STEP},
    {".stp", EXCHANGE_FILE_TYPE::STEP},
    {".iges", EXCHANGE_FILE_TYPE::IGES},
    {".igs", EXCHANGE_FILE_TYPE::IGES},
    {".stl", EXCHANGE_FILE_TYPE::STL},
    {".obj", EXCHANGE_FILE_TYPE::OBJ},
    {".gltf", EXCHANGE_FILE_TYPE::GLTF},
    {".glb", EXCHANGE_FILE_TYPE::GLTF},
    {".dxf", EXCHANGE_FILE_TYPE::DXF},
    {".dwg", EXCHANGE_FILE_TYPE::DWG},
    {".brep", EXCHANGE_FILE_TYPE::BREP},
    {".amx", EXCHANGE_FILE_TYPE::AMX},
#ifdef INTEROP_ENABLE
    {".iam", EXCHANGE_FILE_TYPE::INTEROP},
    {".ipt", EXCHANGE_FILE_TYPE::INTEROP},
    {".sat", EXCHANGE_FILE_TYPE::INTEROP},
    {".sab", EXCHANGE_FILE_TYPE::INTEROP},
    {".model", EXCHANGE_FILE_TYPE::INTEROP},
    {".x_t", EXCHANGE_FILE_TYPE::INTEROP},
    {".x_b", EXCHANGE_FILE_TYPE::INTEROP},
    {".sldprt", EXCHANGE_FILE_TYPE::INTEROP},
    {".sldasm", EXCHANGE_FILE_TYPE::INTEROP},
    {".catpart", EXCHANGE_FILE_TYPE::INTEROP},
    {".catproduct", EXCHANGE_FILE_TYPE::INTEROP},
    {".cgr", EXCHANGE_FILE_TYPE::INTEROP},
#endif
} ;

EXCHANGE_FILE_TYPE get_file_type_by_extension_with_dot(const std::string& extension_with_dot)
{
    // LOG_INFO("extension_with_dot:{}",extension_with_dot) ;
    auto it = FILE_EXTENSION_NAME_TYPE_MAP.find(extension_with_dot) ;
    if (it == FILE_EXTENSION_NAME_TYPE_MAP.end()) {
        return EXCHANGE_FILE_TYPE::NONE ;
    }
    return it->second ;
}

bool is_support_file(std::string file_path)
{
    if (!AMCAX::Exchange::file_util::is_file_exist(file_path)){
        
        return false ;
    }
    auto file_type = get_file_type_by_extension_with_dot(file_util::get_extension(file_path)) ;
    if (file_type == EXCHANGE_FILE_TYPE::NONE) {
        return false ;
    }
    
    return true ;
}

void SetupLog()
{
    std::cout << "setup" << std::endl ;
    LOG_INFO("setup spdlog") ;
    init_spdlog() ;
}

std::shared_ptr<ExchangeIO> Create(const std::string &filePath ,bool isWrite) 
{
    if (!isWrite && !is_support_file(filePath)) {
        LOG_ERROR("unsupport files: {}",filePath) ;
        return nullptr ;
    }
    
    auto file_type = get_file_type_by_extension_with_dot(file_util::get_extension(filePath)) ;
    switch (file_type) {
    #ifdef AMCAX_STEP_ENABLE
        case EXCHANGE_FILE_TYPE::STEP:
            return std::make_shared<ExchangeStep>(filePath) ;
    #else
        #ifdef INTEROP_ENABLE
            case EXCHANGE_FILE_TYPE::STEP:
                return std::make_shared<InterOp::ExchangeInterOp>(filePath) ;
        #endif
    #endif
        case EXCHANGE_FILE_TYPE::OBJ:
            // return new ExchangeObj(file_path) ;
            return std::make_shared<ExchangeObj>(filePath) ;
        // case EXCHANGE_FILE_TYPE::STL:
        //     return new ExchangeStl(file_path) ;
        //    return std::make_shared<ExchangeStl>(filePath) ;
        case EXCHANGE_FILE_TYPE::GLTF:
            return std::make_shared<ExchangeGltf>(filePath,file_util::get_extension(filePath)) ;
            // return std::make_shared<ExchangeStl>(filePath) ;
        case EXCHANGE_FILE_TYPE::BREP:
        //     return new ExchangeBrep(file_path) ;
            return std::make_shared<ExchangeBrep>(filePath) ;
        case EXCHANGE_FILE_TYPE::DXF:
        //     return new ExchangeDxf(file_path) ;
            return std::make_shared<ExchangeDxf>(filePath) ;
        case EXCHANGE_FILE_TYPE::DWG:
        //     return new ExchangeDwg(file_path) ;
            return std::make_shared<ExchangeDwg>(filePath) ;
#ifdef INTEROP_ENABLE
        case EXCHANGE_FILE_TYPE::INTEROP:
        //     return new ExchangeInterOp(file_path) ;
            return std::make_shared<InterOp::ExchangeInterOp>(filePath) ;
#endif

#ifdef AMXSTORAGE_ENABLE
        case EXCHANGE_FILE_TYPE::AMX:
            return std::make_shared<ExchangeAmx>(filePath) ;
            // return new ExchangeAmx(file_path) ;
            break;
#endif

        default:
            return nullptr ;
    }
    return nullptr ;
} 

std::shared_ptr<ExchangeIO> Create(std::ifstream *in_file_stream,std::ofstream *out_file_stream, std::string file_type_str,bool is_write)
{
    if (file_type_str.empty()) {
        LOG_ERROR("file type is empty") ;
        return nullptr ;
    }
    std::string real_type_name=file_type_str ;
    if (0 != file_type_str.compare(0,1,".")) {  
        LOG_DEBUG("file type<{}> is not start with dot",file_type_str) ;
        real_type_name="."+file_type_str ;
    }
    auto file_type = get_file_type_by_extension_with_dot(real_type_name) ;
    if (file_type == EXCHANGE_FILE_TYPE::NONE) {
        LOG_ERROR("unsupport files") ;
        return nullptr ;
    }
    switch (file_type) {
        case EXCHANGE_FILE_TYPE::STEP:
            return std::make_shared<ExchangeStep>(in_file_stream,out_file_stream) ;
            break;
        case EXCHANGE_FILE_TYPE::OBJ:
            return std::make_shared<ExchangeObj>(in_file_stream,out_file_stream) ;
            break;
        case EXCHANGE_FILE_TYPE::STL:
            return std::make_shared<ExchangeStl>(in_file_stream,out_file_stream) ;
            break;
        case EXCHANGE_FILE_TYPE::GLTF:
            return std::make_shared< ExchangeGltf>(in_file_stream,out_file_stream,real_type_name) ;
        case EXCHANGE_FILE_TYPE::BREP:
            return std::make_shared< ExchangeBrep>(in_file_stream,out_file_stream) ;
        case EXCHANGE_FILE_TYPE::DXF:
            return std::make_shared< ExchangeDxf>(in_file_stream,out_file_stream) ;
        case EXCHANGE_FILE_TYPE::DWG:
            LOG_ERROR("DWG files do not support stream operations.") ;
            break;
        // case EXCHANGE_FILE_TYPE::INTEROP:
        //     return std::make_shared< InterOp::ExchangeInterOp>(in_file_stream,out_file_stream) ;

#ifdef AMXSTORAGE_ENABLE
        // case EXCHANGE_FILE_TYPE::AMX:
            // return std::make_shared< ExchangeAmx>(in_file_stream,out_file_stream) ;
            // return new ExchangeAmx(in_file_stream,out_file_stream) ;
#endif

        default:
            return nullptr ;
    }
    return nullptr ;
}

std::shared_ptr<ExchangeIO> Create(std::ifstream *in_file_stream,const std::string &fileType) 
{
    return Create(in_file_stream,nullptr,fileType,false) ;
}
std::shared_ptr<ExchangeIO> Create(std::ofstream *out_file_stream,const std::string &fileType) 
{
    return Create(nullptr,out_file_stream,fileType,true) ;
}

std::shared_ptr<ExchangeIO> CreateAmx(std::shared_ptr<std::fstream> in_file_stream,std::shared_ptr<std::fstream> out_file_stream) 
{
    return std::make_shared< ExchangeAmx>(in_file_stream,out_file_stream) ;
    // return nullptr ;
}

ExchangeBase::ExchangeBase(std::string file_path)
#ifndef CXX_LESS_17
:filePath_(std::filesystem::u8path(file_path).string()),
#else
:filePath_(file_path),
#endif
inputFileType_(INPUT_FILE_TYPE::FILE_PATH),
outputFileType_(OUTPUT_FILE_TYPE::FILE_PATH)
{
    fileType_ = get_file_type_by_extension_with_dot(file_util::get_extension(file_path)) ;
    if (fileType_ == EXCHANGE_FILE_TYPE::NONE) {
        LOG_ERROR("file type not support: {}", file_path) ;
        throw std::runtime_error("file type not support") ;
    }
} 

ExchangeBase::ExchangeBase(std::ifstream *in_file_stream,std::ofstream *out_file_stream, EXCHANGE_FILE_TYPE file_type)
:inFileStream_(in_file_stream) ,
outFileStream_(out_file_stream),
fileType_(file_type),
inputFileType_(INPUT_FILE_TYPE::FILE_INPUT_STREAM),
outputFileType_(OUTPUT_FILE_TYPE::FILE_OUTPUT_STREAM)
{
    if (EXCHANGE_FILE_TYPE::NONE == file_type) {
        throw std::runtime_error("file type not support") ;
    }
    // file_type_ = file_type ;
    // inFileStream_ = file_stream;
}

ExchangeBase::ExchangeBase(std::shared_ptr<std::fstream> in_file_stream_amx,std::shared_ptr<std::fstream> out_file_stream_amx, EXCHANGE_FILE_TYPE file_type)
:inFileStreamAmx_(in_file_stream_amx) ,
outFileStreamAmx_(out_file_stream_amx),
fileType_(file_type),
inputFileType_(INPUT_FILE_TYPE::FILE_INPUT_STREAM),
outputFileType_(OUTPUT_FILE_TYPE::FILE_OUTPUT_STREAM)
{
    if (EXCHANGE_FILE_TYPE::NONE == file_type) {
        throw std::runtime_error("file type not support") ;
    }
    // file_type_ = file_type ;
    // inFileStream_ = file_stream;
}

void ExchangeBase::NestedExchangeShapeToFlatRecursion(std::vector<std::shared_ptr<ExchangeObject>>& flat_shapes,std::shared_ptr<ExchangeObject> raw_shape)
{
    if (raw_shape->childs.size() > 0)
    {
        for(auto& child_exchange_shape : raw_shape->childs)
        {
            flat_shapes.push_back(child_exchange_shape);
            NestedExchangeShapeToFlatRecursion(flat_shapes, child_exchange_shape);
        }
    }
}

void ExchangeBase::NestedExchangeShapeToFlat(std::vector<std::shared_ptr<ExchangeObject>>& flat_shapes,std::vector<std::shared_ptr<ExchangeObject>> raw_shape)
{
    for(auto& raw_shape_item : raw_shape)
    {
        flat_shapes.push_back(raw_shape_item);
        NestedExchangeShapeToFlatRecursion(flat_shapes, raw_shape_item);
    }
}


void ExchangeBase::TransformToPartToposhapeRecursion(const std::shared_ptr<ExchangeDocumentReference> &reference,const  ExchangeMatrix transfrom){
    bool isPart = reference->IsPart() ;
    if(!isPart){
        for (auto &childInstance : reference->GetDirectChildInstances()){
            auto childReference = childInstance->GetDocumentReference();
            auto childTransform = childInstance->GetTransform();
            
            // FIXME::
            TransformToPartToposhapeRecursion(childReference, transfrom.LeftMultiply(childTransform)) ;
        }
        return ;
    }
    // 平移
    auto exchangeObject = std::make_shared<ExchangeObject>(string_util::generate_UUID()) ;
    std::shared_ptr<ExchangeTopoShape> exchangeShape = nullptr ;
    reference->GetPartShape(exchangeShape);
    exchangeShape->ApplyTransfrom(transfrom) ;
    exchangeObject->shape = exchangeShape ;
    exchangePartObjectList_.push_back(exchangeObject) ;
}

void ExchangeBase::ToPart(){
    if(!exchangeDocumentRootInstance_){
        return ;
    }
    auto exchangeDocumentReference = exchangeDocumentRootInstance_->GetDocumentReference();
    if (!exchangeDocumentReference)
    {
        LOG_ERROR("exchangeDocumentReference is null");
        return ;
    }
    bool isPart = exchangeDocumentReference->IsPart();
    std::shared_ptr<ExchangeTopoShape> exchangeShape = nullptr ;
    exchangeDocumentReference->GetPartShape(exchangeShape);
    auto trsf = exchangeDocumentRootInstance_->GetTransform();
    if(!isPart){
        TransformToPartToposhapeRecursion(exchangeDocumentReference, trsf) ;
        return ;
    }
    auto exchangeObject = std::make_shared<ExchangeObject>(string_util::generate_UUID()) ;
    exchangeObject->shape = exchangeShape ;
    exchangePartObjectList_.push_back(exchangeObject) ;
}

void ExchangeBase::Read()
{
    if (
        (INPUT_FILE_TYPE::UNKNOWN == inputFileType_)
        || 
        (INPUT_FILE_TYPE::FILE_PATH != inputFileType_ && INPUT_FILE_TYPE::FILE_INPUT_STREAM != inputFileType_)
    ) {
        LOG_DEBUG("input file type:{}",(int)inputFileType_) ;
        return ;
    }

    if (INPUT_FILE_TYPE::FILE_PATH == inputFileType_ && AMCAX::Exchange::file_util::is_file_exist(filePath_)) {
        if (!AMCAX::Exchange::file_util::is_file_exist(filePath_)) {
            LOG_ERROR("file not exist:{}",filePath_) ;
            return ;
        }
        ReadFromFilePath() ;
    }
    else if(INPUT_FILE_TYPE::FILE_INPUT_STREAM == inputFileType_) {
        if(nullptr == inFileStream_ && nullptr == inFileStreamAmx_){
            LOG_ERROR("input file stream is null") ;
            return ;
        }
        ReadFromFilePath() ;
    }else {
        LOG_ERROR("input file type is unknown") ;
        return  ;
    }
}

// void ExchangeBase::write(const std::vector<std::shared_ptr<ExchangeObject>>& exchange_shapes)
// {
//     if (OUTPUT_FILE_TYPE::FILE_PATH == outputFileType_) {
//         write_to_file_path(exchange_shapes) ;
//     }
//     else if (OUTPUT_FILE_TYPE::FILE_OUTPUT_STREAM == outputFileType_) {
//         write_to_file_stream(exchange_shapes) ;
//     }
//     else {
//         LOG_ERROR("output file type is unknown") ;
//     }
// }

void ExchangeIO::Convert(const std::string &inFilePath, const std::string &outFilePath) 
{
    #ifdef INTEROP_ENABLE
    FusionConvert(inFilePath,outFilePath);
    #endif
}

std::shared_ptr<ExchangeParameter> ExchangeIO::GetMetaData(const std::string &inFilePath, const std::string &outFilePath, const std::shared_ptr<ExchangeParameter> &inputParameter)
{
    ExchangeParameter *resultMetaDataInfo = nullptr;
    #ifdef INTEROP_ENABLE
    resultMetaDataInfo = GetMetaDataByInterOp(inFilePath,outFilePath, inputParameter);
    #endif
    return std::shared_ptr<ExchangeParameter>(resultMetaDataInfo);
}

void ExchangeIO::GetDocumentInfo(const std::string &inFilePath, std::string &outJson)
{
    if(InterOp::ExchangeInterOp::CheckLicense())
    {
        LOG_INFO("InterOp license check success") ;
    }
    else
    {
        LOG_ERROR("InterOp license check failed") ;
        return ;
    }
#ifdef INTEROP_ENABLE
    AMCAX::Exchange::InterOp::IO::GetInstance().GetDocumentInfo(inFilePath, outJson) ;
#endif
}

void ExchangeIO::GetProperties(const std::string &inFilePath, std::string &outJson, bool includingChildren)
{
    if(InterOp::ExchangeInterOp::CheckLicense())
    {
        LOG_INFO("InterOp license check success") ;
    }
    else
    {
        LOG_ERROR("InterOp license check failed") ;
        return ;
    }

#ifdef INTEROP_ENABLE
    AMCAX::Exchange::InterOp::IO::GetInstance().GetProperties(inFilePath, outJson, includingChildren) ;
#endif
}

void ExchangeIO::SetReporter(ExchangeReporterType operation) {
    ExchangeHelper::GetInstance().SetReporter(operation) ;
}

std::unordered_map<std::string,Core::PMI::ExchangeShapePMIInfos> ExchangeIO::GetPMI(const std::string &filePath)
{
    std::unordered_map<std::string,AMCAX::Exchange::InterOp::PMI::InterOpShapePMIInfos> pmi  = AMCAX::Exchange::InterOp::IO::GetInstance().GetPMIInfo(filePath)  ;

    std::unordered_map<std::string,Core::PMI::ExchangeShapePMIInfos> result  ;
    PMIHelper::ConvertInterOpPMI2ExchangePMI(pmi,result) ;
    return result ;
}

AMCAX_EXCHANGE_NAMESPACE_END